﻿using System;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Threading;

namespace ParallelUtils
{
    public class Tessellator
    {
        public delegate void ProcessChunkEventHandler(BitmapChunk chunk);
        public delegate void ProgressChangedEventHandler(int pct);
        public delegate void CompletedEventHandler(int milliseconds);

        public event ProcessChunkEventHandler ProcessChunk;
        public event ProgressChangedEventHandler ProgressChanged;
        public event CompletedEventHandler Completed;

        public int Width { get; private set; }
        public int Height { get; private set; }
        public WriteableBitmap Bitmap { get; private set; }
        public bool IsBusy { get; private set; }
        public bool IsStopRequested { get; private set; }

        private readonly int chunkSize;
        private readonly int horizontalChunkCount, verticalChunkCount, totalChunkCount;
        private readonly int[] sequenceIndex;

        private int startingTickCount;

        public Tessellator(int width, int height, int chunkSize = 50)
        {
            Width = width;
            Height = height;
            this.chunkSize = chunkSize;

            horizontalChunkCount = (int)Math.Ceiling((decimal)width / chunkSize);
            verticalChunkCount = (int)Math.Ceiling((decimal)height / chunkSize);
            totalChunkCount = horizontalChunkCount * verticalChunkCount;

            sequenceIndex = new int[totalChunkCount];
            OrderSequence();

            Bitmap = new WriteableBitmap(
                width, height,
                96, 96,
                PixelFormats.Bgr32,
                null);
        }

        public void OrderSequence()
        {
            for (var i = 0; i < totalChunkCount; i++)
                sequenceIndex[i] = i;
        }
        public void ShuffleSequence()
        {
            //Durstenfeld-Fisher-Yates shuffle
            var rnd = new Random();
            for (var i = totalChunkCount - 1; i > 0; i--)
            {
                var j = rnd.Next(i);
                var tmp = sequenceIndex[i];
                sequenceIndex[i] = sequenceIndex[j];
                sequenceIndex[j] = tmp;
            }
        }
        public void ClearBitmap()
        {
            new BitmapChunk(0, 0, Width, Height).UpdateBitmap(Bitmap);
        }
        public void BackgroundRun()
        {
            var thread = new Thread(new ThreadStart(() =>
            {
                startingTickCount = Environment.TickCount;
                Tessellate();
                if (Completed != null)
                    Completed(Environment.TickCount - startingTickCount);

                IsStopRequested = false;
            }));
            thread.Priority = ThreadPriority.Lowest;

            thread.Start();
        }
        public void RequestStop()
        {
            IsStopRequested = true;
        }
        public void ExportPNG(string fileName)
        {
            using (var stream = new FileStream(fileName, FileMode.Create))
            {
                var encoder = new PngBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(Bitmap));
                encoder.Save(stream);
            }
        }

        private void Tessellate()
        {
            IsBusy = true;

            var completedCount = 0;
            Parallel.For(0, totalChunkCount, (j, loopState) =>
            {
                var i = sequenceIndex[j];
                int w = chunkSize * (i % horizontalChunkCount);
                int h = chunkSize * (int)(i / horizontalChunkCount);

                var chunk = new BitmapChunk(w, h, Math.Min(chunkSize, Width - w), Math.Min(chunkSize, Height - h));

                if (ProcessChunk != null)
                    ProcessChunk(chunk);

                chunk.UpdateBitmap(Bitmap);

                completedCount++;

                if (ProgressChanged != null)
                    ProgressChanged(100 * completedCount / totalChunkCount);

                if (IsStopRequested)
                    loopState.Stop();
            });

            IsBusy = false;
        }
    }
}
